﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using YasharEl.Infrastructure.ProcessEngine.DomainModel.ProcessMetadata;
using YasharEl.Infrastructure.ObjectFactory;
using YasharEl.Infrastructure.Diagnostics.Logging;
using System.Diagnostics.Contracts;
using YasharEl.Infrastructure.ExceptionHandling;
using YasharEl.Infrastructure.ProcessEngine.PersistenceModel.Repositories;
using YasharEl.Infrastructure.ResourcesManagement;
using YasharEl.Infrastructure.ProcessEngine.PersistenceModel.Specifications;
using YasharEl.Infrastructure.Core.PersistenceModel.Applications;
using YasharEl.Infrastructure.Core.Applications;

namespace YasharEl.Infrastructure.ProcessEngine.DomainModel.Implementation.ProcessMetadata
{
    public class DefaultProcessMetadataCatalog : IProcessMetadataCatalog
    {
        #region ReadOnly Fields

        private readonly IDependencyResolver resolver;
        private readonly ILogger<DefaultProcessMetadataCatalog> logger;
        private readonly IExceptionManager exceptionManager;
        private readonly IProcessDefinitionsRepository processDefinitionsRepository;
        private readonly IApplicationsRepository applicationsRepository;

        #endregion

        #region Constructors

        public DefaultProcessMetadataCatalog(
            IDependencyResolver resolver, 
            ILogger<DefaultProcessMetadataCatalog> logger, 
            IExceptionManager exceptionManager,
            IProcessDefinitionsRepository processDefinitionsRepository,
            IApplicationsRepository applicationsRepository)
        {
            Contract.Requires<ArgumentNullException>(resolver != null, "resolver could not be null.");
            Contract.Requires<ArgumentNullException>(logger != null, "logger could not be null.");
            Contract.Requires<ArgumentNullException>(exceptionManager != null, "exceptionManager could not be null.");
            Contract.Requires<ArgumentNullException>(processDefinitionsRepository != null, "processDefinitionsRepository could not be null.");
            Contract.Requires<ArgumentNullException>(applicationsRepository != null, "applicationsRepository could not be null.");

            this.resolver = resolver;
            this.logger = logger;
            this.exceptionManager = exceptionManager;
            this.processDefinitionsRepository = processDefinitionsRepository;
            this.applicationsRepository = applicationsRepository;
        }

        #endregion

        #region IProcessMetadataCatalog Members

        public IProcessDefinition GetProcessDefinition(long applicationId, long userId, long processDefinitionId)
        {
            logger.Debug("Process definition {0} requested by user {1}...", processDefinitionId, userId);
            IProcessDefinition processDefinition = null;
            try
            {
                IApplication application = applicationsRepository.GetById(applicationId);
                if (application == null)
                {
                    throw new ApplicationNotFoundException(applicationId);
                }

                processDefinition = processDefinitionsRepository.GetById(processDefinitionId);
                if (processDefinition != null)
                {
                    if (processDefinition.BelongsToGroup.BelongsToApplication != application)
                    {
                        logger.Warn("Process definition {0} requested for incorrect application reference {1} by user {2}.", processDefinitionId, applicationId, userId);
                        throw new ProcessDefinitionAccessDeniedException(
                            resolver.Resolve<IResourceManager>(), 
                            applicationId, 
                            processDefinitionId
                        );
                    }
                }
            }
            catch (Exception e)
            {
                bool rethrow = exceptionManager.HandleException(e, ProcessEngineConstants.DomainLayerExceptionPolicy);
                if (rethrow)
                {
                    throw;
                }
            }

            return processDefinition;
        }

        public IList<IProcessDefinition> GetAvailableProcessDefinitions(long applicationId, long userId)
        {
            logger.Debug("All available process definitions for application {0} requested by user {1}...", applicationId, userId);

            IList<IProcessDefinition> availableProcessDefinitions = new List<IProcessDefinition>();
            try
            {
                IApplication application = applicationsRepository.GetById(applicationId);
                if (application == null)
                {
                    throw new ApplicationNotFoundException(applicationId);
                }

                availableProcessDefinitions = processDefinitionsRepository
                    .Specify<IProcessDefinitionSpecification>()
                    .BelongsToApplication(applicationId)
                    .ToResult()
                    .ToList();
            }
            catch (Exception e)
            {
                bool rethrow = exceptionManager.HandleException(e, ProcessEngineConstants.DomainLayerExceptionPolicy);
                if (rethrow)
                {
                    throw;
                }
            }

            return availableProcessDefinitions;
        }

        #endregion
    }
}
